package com.xy6.algo.sort;

import java.util.Arrays;


/**
 * 堆排序
 * 
 * <pre>
 * 时间复杂度：O(nlgn)，空间复杂度：O(c)
 * 
 * 将数组堆化：
 * 遍历从最后一个根节点（是根节点，而非节点，节点i的根节点为(i-1)/2）位置到0，当前索引为i
 * 比较父节点（p=(i-1)/2）、左子节点（l=2p+1）、右子节点（r=2p+2），将三个中最大值放到父节点位置。
 * 交换元素可能引起其他元素不遵循堆的规则，因此如果发生交换，
 * 则将最大元素相关的节点（父节点、左子节点、右子节点）再次进行堆化，递归进行，直至所有元素都遵循堆的规则。
 * 遍历i的前一个元素，按照上述操作。
 * 直至遍历至数组第一个元素。数组已完成堆化，遵循最大堆规则。
 * 
 * 对数组进行排序：
 * 遍历从最后一个元素到第一个元素，当前索引为i
 * 将第一个元素交换到i元素进行交换，实现最大元素放在数组后面
 * 将前面n-1个元素进行堆化。
 * 遍历i的前一个元素，按照上述操作。
 * 直至遍历至数组第一个元素。数组元素已按从小到大排序。
 * 
 * 原地排序，需要常数个存储空间。
 * 不稳定排序。
 * </pre>
 * 
 * @author zhang
 * @since 2017-08-01
 */
public class SortHeap {

	public static void main(String[] args) {
		int[] arr = new int[] { 1, 3, 5, 2, 4, 6 };
		sort(arr);
		System.err.println(Arrays.toString(arr));
		print(arr);
	}

	/**
	 * 堆排序
	 * 
	 * @param arr
	 */
	public static void sort(int[] arr){
		buildMaxHeapify(arr);
		heapSort(arr);
	}
	
	/**
	 * 将数组堆化
	 * 
	 * @param data
	 */
	private static void buildMaxHeapify(int[] data) {
		//没有子节点的才需要创建最大堆，从最后一个的父节点开始
		int startIndex = getParentIndex(data.length - 1);
		//从尾端开始创建最大堆，每次都是正确的堆
		for (int i = startIndex; i >= 0; i--) {
			maxHeapify(data, data.length, i);
		}
	}

	/**
	 * 创建最大堆
	 *
	 * @param data
	 * @param heapSize 需要创建最大堆的大小，一般在sort的时候用到，因为最多值放在末尾，末尾就不再归入最大堆了
	 * @param index 当前需要创建最大堆的位置
	 */
	private static void maxHeapify(int[] data, int heapSize, int index) {
		//当前点与左右子节点比较
		int left = getChildLeftIndex(index);
		int right = getChildRightIndex(index);

		int largest = index;
		if (left < heapSize && data[index] < data[left]) {
			largest = left;
		}
		if (right < heapSize && data[largest] < data[right]) {
			largest = right;
		}
		//得到最大值后可能需要交换，如果交换了，其子节点可能就不是最大堆了，需要重新调整
		if (largest != index) {
			int temp = data[index];
			data[index] = data[largest];
			data[largest] = temp;
			maxHeapify(data, heapSize, largest);
		}
	}

	/**
	 * 排序
	 * <p>最大堆排序。最大值放在末尾，data虽然是最大堆，在排序后就成了递增的
	 *
	 * @param data
	 */
	private static void heapSort(int[] data) {
		//末尾与头交换，交换后调整最大堆
		for (int i = data.length - 1; i > 0; i--) {
			int temp = data[0];
			data[0] = data[i];
			data[i] = temp;
			//缩小集合长度，剔除后面已排序的元素集
			maxHeapify(data, i, 0);
		}
	}

	/**
	 * 父节点位置
	 *
	 * @paramcurrent
	 * @return
	 */
	private static int getParentIndex(int current) {
		// (i-1)/2
		return (current - 1) >> 1;
	}

	/**
	 * 左子节点position注意括号，加法优先级更高
	 *
	 * @paramcurrent
	 * @return
	 */
	private static int getChildLeftIndex(int current) {
		// 2*i+1
		return (current << 1) + 1;
	}

	/**
	 * 右子节点position
	 *
	 * @paramcurrent
	 * @return
	 */
	private static int getChildRightIndex(int current) {
		return (current << 1) + 2;
	}

	private static void print(int[] data) {
		int pre = -2;
		for (int i = 0; i < data.length; i++) {
			if (pre < (int) getLog(i + 1)) {
				pre = (int) getLog(i + 1);
				System.out.println();
			}
			System.out.print(data[i] + "|");
		}
	}

	/**
	 * 以2为底的对数
	 *
	 * @paramparam
	 * @return
	 */
	private static double getLog(double param) {
		return Math.log(param) / Math.log(2);
	}
}
